﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using PloobsEngine.Utils;
using Etapa1.SofRender;

namespace Etapa1
{
    /// <summary>
    /// TOP 5 - MOST HARD CLASS I EVERRRRR MADE 
    /// Barycentric Image http://hairrendering.files.wordpress.com/2010/06/barycentric_coordinates.png
    /// </summary>
    public class Tesselation
    {
        

        /// <summary>
        /// [0,1] -> [-1,1]
        /// E Invert Y
        /// </summary>
        /// <param name="vec"></param>
        /// <returns></returns>
        Vector4 MapToScreen(Vector4 vec)
        {
            return new Vector4((vec.X * 2) - 1, ( (1 - vec.Y) * 2) - 1, vec.Z, 1);
        }

        static Dictionary<String, List<Vector3>> cache = new Dictionary<string, List<Vector3>>();
        List<Vector3> vertices;
        public List<Vector3> TesselateTriangle(float f1, float f2,float f3)
        {            
            if (f1 < 1)
                f1 = 1;
            if (f2 < 1)
                f2 = 1;
            if (f3 < 1)
                f3 = 1;

            String cacheKey = f1 + "#" + f2 + "#" + f3;
            if (cache.ContainsKey(cacheKey))
                return cache[cacheKey];

            vertices = new List<Vector3>();            
                          
            float factor = Math.Max(Math.Max(f1, f2), f3);
            CreateSubTriangleInBaricentricCoordinate(factor, f1, new Vector3(1f / 3f), Vector3.UnitY, new Vector3(0.5f, 0.5f, 0));
            CreateSubTriangleInBaricentricCoordinate(factor, f2, new Vector3(1f / 3f), Vector3.UnitY, new Vector3(0, 0.5f, 0.5f));
            CreateSubTriangleInBaricentricCoordinate(factor, f3, new Vector3(1f / 3f), Vector3.UnitZ, new Vector3(0.5f, 0, 0.5f));
            CreateSubTriangleInBaricentricCoordinate(factor, f2, new Vector3(1f / 3f), Vector3.UnitZ, new Vector3(0, 0.5f, 0.5f));
            CreateSubTriangleInBaricentricCoordinate(factor, f1, new Vector3(1f / 3f), Vector3.UnitX, new Vector3(0.5f, 0.5f, 0));
            CreateSubTriangleInBaricentricCoordinate(factor, f3, new Vector3(1f / 3f), Vector3.UnitX, new Vector3(0.5f, 0, 0.5f));

            cache[cacheKey] = vertices;
            return vertices;

            {
                //VertexType v = new VertexType();
                //v[Slots.POSITION0] = MapToScreen(new Vector4(0,1,0,1));
                //all.Add(v);
                //v = new VertexType();
                //v[Slots.POSITION0] = MapToScreen(new Vector4(0.5f, 0, 0, 1));
                //all.Add(v);
                //v = new VertexType();
                //v[Slots.POSITION0] = MapToScreen(new Vector4(1, 0, 0, 1));
                //all.Add(v);                
            }            
        }


        public void CreateSubTriangleInBaricentricCoordinate(float fmax,float f,Vector3 Center, Vector3 Tvert, Vector3 Ipoint)
        {            

            int fExceptionCase = (int)Math.Ceiling(f);
            int horizontal = (int)Math.Ceiling(fmax);
            int it = horizontal;
            Vector3 start = Tvert;
            Vector3 stepX = Lerp(Tvert, Ipoint, (1f / fmax)) - Tvert;
            Vector3 stepXY = Lerp(Tvert, Center, (1f / fmax)) - Tvert;
            Vector3 stepY = stepXY - stepX;


            Vector3 FstepX;
            Vector3 FstepXY ;
            Vector3 FstepY ;
            if (horizontal == fmax)
            {
                FstepX = Lerp(Tvert, Ipoint, (1f / fmax)) - Tvert;
                FstepXY = Lerp(Tvert, Center, (1f / fmax)) - Tvert;                
            }
            else
            {
                FstepX = Ipoint - Lerp(Tvert, Ipoint, (1f / fmax) * (horizontal - 1));
                FstepXY = Center - Lerp(Tvert, Center, (1f / fmax) * (horizontal - 1));                
            }

            FstepY = FstepXY - FstepX;
            
            for (int i = 0; i < horizontal; i++)
            {
                ///EXCEPTIONAL CASE: Diferentes Factors
                if (i == 0 && fmax != f)
                {

                    Vector3 NFstepX;
                    if (f == fExceptionCase)
                    {
                        NFstepX = Lerp(Tvert, Ipoint, (1f / f)) - Tvert;                    
                    }
                    else
                    {
                        NFstepX = Ipoint - Lerp(Tvert, Ipoint, (1f / f) * (fExceptionCase - 1));                        
                    }

                    Vector3 NupStart = start + stepXY;
                    Vector3 NStart = start;
                    Vector3 NstepX = Lerp(Tvert, Ipoint, (1f / f)) - Tvert;
                    Vector3 XstepX = stepX;
                    
                    bool state = true;
                    bool lastState = false;
                    
                    QMachine qm = new QMachine(fExceptionCase, horizontal - 1);
                    for (int l = 0; l < fExceptionCase + horizontal - 1; l++)
                    {
                        if (l == fExceptionCase + horizontal - 3)
                        {
                            NstepX = NFstepX;
                            XstepX = FstepX;
                        }

                        state = qm.getNext();
                        if (state == lastState) ///Nao mudou de estado
                        {
                            if (state == true)
                            {///CONTINUA true

                                generateTriangle(NupStart, NStart, NStart + NstepX);
                                NStart += NstepX;
                            }
                            else
                            {///CONTINUA False
                                generateTriangle(NStart, NupStart, NupStart + XstepX);
                                NupStart += XstepX;
                            }
                        }
                        else ///mudou de estado
                        {
                            if (state == true)
                            {///false to true

                                generateTriangle(NupStart, NStart, NStart + NstepX);
                                NStart += NstepX;
                            }
                            else
                            {///true to False
                                generateTriangle(NStart, NupStart, NupStart + XstepX);
                                NupStart += XstepX;
                            }
                            lastState = state;
                        }
                    }

                    start += stepXY;
                    it--;                
                    continue;
                }


                if (i == horizontal - 1)
                {
                    ////OneTriangle Fractional Case
                    Vector3 p0 = start;
                    Vector3 p1 = start + FstepX;
                    Vector3 p2 = start + FstepXY;
                    generateTriangle(p0, p1, p2);
                }                
                else
                {
                    ////OneTriangle integer Case
                    Vector3 p0 = start;
                    Vector3 p1 = start + stepX;
                    Vector3 p2 = start + stepXY;
                    generateTriangle(p0, p1, p2);                    
                }

                for (int j = 1; j <= it - 1; j++)
                {   
                    if (j == it - 1)
                    {
                        ////2Triangle integer Case
                        Vector3 p0 = start + stepX * (j); 
                        Vector3 p1 = p0 + FstepX;                        
                        Vector3 p2 = p0 + stepY;
                        Vector3 p3 = p0 + stepY + FstepX;
                        generateTriangle(p3, p2, p0);
                        generateTriangle(p3, p0, p1);
                    }                    
                    else
                    {
                        ////2Triangle integer Case
                        Vector3 p0 = start + stepX * j;
                        Vector3 p1 = start + stepX * (j + 1);
                        Vector3 p2 = p0 + stepY;
                        Vector3 p3 = p0 + stepXY;
                        generateTriangle(p3, p2, p0);
                        generateTriangle(p3, p0, p1);
                    }
                }
                start += stepXY;
                it--;                
            }            
        }

        Vector3 GetCartesian(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 bari)
        {
            return p0 * bari.X + p1 * bari.Y + p2 * bari.Z;
        }

        
        private void generateTriangle(Vector3 p0, Vector3 p1, Vector3 p2)
        {
            //Vector3 e0 = new Vector3(0.5f, 1, 0);
            //Vector3 e1 = new Vector3(0,0,0);
            //Vector3 e2 = new Vector3(1,0,0);

            //VertexType v0 = new VertexType();
            //p0 = GetCartesian(e0,e1,e2,p0);
            //v0[Slots.POSITION0] = MapToScreen(new Vector4(p0.X, p0.Y, p0.Z, 1));
            vertices.Add(p0);
            //VertexType v1 = new VertexType();
            //p1 = GetCartesian(e0, e1, e2, p1);            
            //v1[Slots.POSITION0] = MapToScreen(new Vector4(p1.X, p1.Y, p1.Z, 1));
            vertices.Add(p1);
            //VertexType v2 = new VertexType();
            //p2 = GetCartesian(e0, e1, e2, p2);            
            //v2[Slots.POSITION0] = MapToScreen(new Vector4(p2.X, p2.Y, p2.Z, 1));
            vertices.Add(p2);
        }                
        
        
        Vector3 Lerp(Vector3 v1, Vector3 v2, float value)
        {
            if (value > 1)
                value = 1;

            float x = MathHelper.Lerp(v1.X, v2.X, value);
            float y = MathHelper.Lerp(v1.Y, v2.Y, value);
            float z = MathHelper.Lerp(v1.Z, v2.Z, value);
            return new Vector3(x, y,z);
        }
    }

    public class QMachine
    {
        float m;
        float n;
        bool state;
        float q;
        public QMachine(float m, float n)
        {
            this.m = m;
            this.n = n;
            q = n - m;
            this.state = true;
        }

        public bool getNext()
        {

            if (state == true)
            {
                if (q >= 0)
                {
                    q -= 2 * m;
                    state = false;
                }
                else
                {
                    q += 2 * n;
                }
            }
            else
            {
                if (q >= 0)
                {
                    q -= 2 * m;
                }
                else
                {
                    q += 2 * n;
                    state = true;
                }
            }
            return state;
        }
    }
}
